The objective of this assignment is to solidify my understanding of rotation, transformation, and other basic operations in computer vision. I will implement these operations from scratch using a programming language (e.g., Python) and basic libraries like NumPy.
pip install opencv-python
Requirement already satisfied: opencv-python in c:\users\tarunbali\anaconda3\lib\site-packages (4.8.0.76) Requirement already satisfied: numpy>=1.21.2 in c:\users\tarunbali\anaconda3\lib\site-packages (from opencv-python) (1.24.3) Note: you may need to restart the kernel to use updated packages.
pip install matplotlib
Requirement already satisfied: matplotlib in c:\users\tarunbali\anaconda3\lib\site-packages (3.7.2) Requirement already satisfied: contourpy>=1.0.1 in c:\users\tarunbali\anaconda3\lib\site-packages (from matplotlib) (1.0.5) Requirement already satisfied: cycler>=0.10 in c:\users\tarunbali\anaconda3\lib\site-packages (from matplotlib) (0.11.0) Requirement already satisfied: fonttools>=4.22.0 in c:\users\tarunbali\anaconda3\lib\site-packages (from matplotlib) (4.25.0) Requirement already satisfied: kiwisolver>=1.0.1 in c:\users\tarunbali\anaconda3\lib\site-packages (from matplotlib) (1.4.4) Requirement already satisfied: numpy>=1.20 in c:\users\tarunbali\anaconda3\lib\site-packages (from matplotlib) (1.24.3) Requirement already satisfied: packaging>=20.0 in c:\users\tarunbali\anaconda3\lib\site-packages (from matplotlib) (23.1) Requirement already satisfied: pillow>=6.2.0 in c:\users\tarunbali\anaconda3\lib\site-packages (from matplotlib) (9.4.0) Requirement already satisfied: pyparsing<3.1,>=2.3.1 in c:\users\tarunbali\anaconda3\lib\site-packages (from matplotlib) (3.0.9) Requirement already satisfied: python-dateutil>=2.7 in c:\users\tarunbali\anaconda3\lib\site-packages (from matplotlib) (2.8.2) Requirement already satisfied: six>=1.5 in c:\users\tarunbali\anaconda3\lib\site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0) Note: you may need to restart the kernel to use updated packages.
import cv2
from matplotlib import pyplot as plt
import numpy as np
# the images have been placed in the same directory as this notebook
images_list=["pic1.jpg", "pic2.jpg", "pic3.jpg","pic4.jpg"]
# function to read image using OpenCV
def read_image(image):
# if the input is a path to the actual image, read it using OpenCV and convert to RGB color format
if isinstance(image,str):
image = cv2.imread(image)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# if the input is already a numpy array of the already read image, then I will simply return the same array
elif isinstance(image,np.ndarray):
image=image
else:
raise ValueError("Check input images!!!") # raise error if any other format is provided as input
return image
# function to display images in one single figure and all the images will be displayed as a sub-figure using matplotlib
def display_images(images):
'''
The input to this function is a list of images which
can be list of paths or list or pre-processed numpy array of pixels.
'''
fig = plt.figure(figsize=(20, 20))
for i, image in enumerate(images):
image=read_image(image)
ax = fig.add_subplot(1, len(images), i+1)
ax.imshow(image)
ax.axis('off')
plt.title("Image No. "+str(i+1))
plt.show()
The read_image() and display_images() functions are going to be used throughout this notebook to ease my work and accomplish the tasks efficiently without repeating same code again and again. This is also an example of modularizing code.
# let's see the original images
display_images(images_list)
def rotation_without_using_opencv(images_list, angle_deg):
'''
The inputs to this function:
1. images_list ==> list of images
2. angle_deg ==> angle by which we wish to rotate our image (in degrees)
'''
tran_images_list=[] # empty list
angle_rad = np.deg2rad(angle_deg) # convert degrees to radians to compute cos and sin values
for i, image in enumerate(images_list):
image=read_image(image)
#original image dimensions
height_orig, width_orig, _ = image.shape
center_orig_x, center_orig_y = width_orig//2, height_orig//2
# pre-calculating the max possible height and width of the rotated image to avoid edges getting cropped
# the transformation matrix is helpful in this calculation
height_tran = round(np.abs(height_orig*np.cos(angle_rad)) + np.abs(width_orig*np.sin(angle_rad)))
width_tran = round(np.abs(width_orig*np.cos(angle_rad)) + np.abs(height_orig*np.sin(angle_rad)))
center_tran_x, center_tran_y = width_tran//2, height_tran//2
# setting up an empty canvas of complete black image to be replaced with image pixels later
tran_image = np.zeros((height_tran,width_tran,image.shape[2])).astype("uint8")
# two 'for' loops (for width and height) will calculate and replace every pixel from the original image onto the canvas
# center points of original and canvas image help in offsetting the image to avoid edge cropping
for w in range(width_tran):
for h in range(height_tran):
#transformation matrix multiplication and offset compensation
x= round((w-center_tran_x)*np.cos(angle_rad)-(h-center_tran_y)*np.sin(angle_rad)+center_orig_x)
y= round((w-center_tran_x)*np.sin(angle_rad)+(h-center_tran_y)*np.cos(angle_rad)+center_orig_y)
# the original image pixels are present only in the range used in the if condition below
if (0<=x<width_orig and 0<=y<height_orig):
tran_image[h,w,:] = image[y,x,:]
tran_images_list.append(tran_image) # keep appending to the list of rotated images
return tran_images_list
# this function uses OpenCV to do the rotation
def rotation_using_opencv(images_list, angle):
'''
The inputs to this function:
1. images_list ==> list of images
2. angle_deg ==> angle by which we wish to rotate our image (in degrees)
'''
tran_images_list=[]
for i, image in enumerate(images_list):
image=read_image(image)
height, width = image.shape[:2]
center = tuple(np.array([height,width])/2)
rotation_matrix = cv2.getRotationMatrix2D(center,angle,1) # using inbuilt method to calculate transformation matrix
image = cv2.warpAffine(image, rotation_matrix, (height,width))
tran_images_list.append(image)
return tran_images_list
# performing rotation operation on the set of images for varrying rotation angles (in degrees)
# so as to compare the outputs of self-defined function and the OpenCV methods
angles_list=[0,30,60,-45,90]
for angle in angles_list:
print(('\033[1m' +"Rotation by "+str(angle)+" degrees").center(100))
# first we display the images rotated without using OpenCV (self-defined)
rotated_images=rotation_without_using_opencv(images_list, angle)
display_images(rotated_images)
# then we display the images rotated using OpenCV
rotated_images=rotation_using_opencv(images_list, angle)
display_images(rotated_images)
print("\n")
Rotation by 0 degrees
Rotation by 30 degrees
Rotation by 60 degrees
Rotation by -45 degrees
Rotation by 90 degrees
The images produced by OpenCV guided me to calculate the dimensions of the rotated image before performing the rotation and that by compensating the offset we can get images without any loss of pixels (edge cropping).
I could have also pre-calculated the dimensions of the rotated images and the center of the rotated image before passing these parameters to the OpenCV functions and then OpenCV would have also produced images without any edge cropping but I didn't do that to present that by default OpenCV doesn't do that ! :)
# function to calculate the transformation matrix for given translation and scaling factors
def transformation_matrix_translation_scaling(translation=(0, 0), scaling=(1, 1)):
'''
Inputs:
1. translation ==> translation factors (dx, dy) [(0,0) by default for No Translation]
2. scaling ==> scaling factors (sx, sy) [(1,1) by default for No Scaling]
'''
dx, dy = translation
sx, sy = scaling
#creating an identity matrix
tran_mat = np.eye(3)
#assigning appropriate translation and scaling factors to the identity matrix to form the transformation matrix
tran_mat[0, 2] = dx
tran_mat[1, 2] = dy
tran_mat[0, 0] = sx
tran_mat[1, 1] = sy
return tran_mat
# fucntion to perform translation and scaling withtout using OpenCV or any other built-in library functions
def translation_scaling_without_opencv(images_list,translation=(0, 0), scaling=(1, 1)):
'''
Inputs:
1. images_list ==> list of images
2. translation ==> translation factors (dx, dy) [(0,0) by default for No Translation]
3. scaling ==> scaling factors (sx, sy) [(1,1) by default for No Scaling]
'''
tran_images_list=[] # empty list
for i, image in enumerate(images_list):
image=read_image(image)
#dimensions of original image
height_orig, width_orig, _ = image.shape
#calculating the transformation matrix using function defined above
tran_matrix=transformation_matrix_translation_scaling(translation, scaling)
# pre-calculating the max possible height and width of the transformed image to avoid edges getting cropped
# if the scaling factors are greater than 1, multiply by the factor after adding the translation factor
# if the scaling factors are lesser than 1, simply add the translation factor without scalar factor multiplication
if tran_matrix[1, 1]>1:
height_tran = round((height_orig+abs(int(tran_matrix[1, 2])))*tran_matrix[1, 1])
else:
height_tran = height_orig+abs(int(tran_matrix[1, 2]))
if tran_matrix[0, 0]>1:
width_tran = round((width_orig+abs(int(tran_matrix[0, 2])))*tran_matrix[0, 0])
else:
width_tran = width_orig+abs(int(tran_matrix[0, 2]))
# setting up an empty canvas of complete black image to be replaced with image pixels later
tran_image = np.zeros((height_tran,width_tran,image.shape[2])).astype("uint8")
# two 'for' loops (for width and height) will calculate and replace every pixel from the original image onto the canvas
for w in range(width_tran):
for h in range(height_tran):
x= round((w-tran_matrix[0, 2])/tran_matrix[0, 0]) # w=x.sx+dx ==> x = (w-dx)/sx
y= round((h-tran_matrix[1, 2])/tran_matrix[1, 1]) # h=y.sy+dy ==> y = (h-dy)/sy
# the original image pixels are present only in the range used in the if condition below
if (0<=x<width_orig and 0<=y<height_orig):
tran_image[h,w,:] = image[y,x,:]
tran_images_list.append(tran_image) # keep appending to the list of transformed images
return tran_images_list
# transforming images by (500,500 translation) and (1,1) scaling
trans_scaled_images = translation_scaling_without_opencv(images_list, (500,500), (1,1))
# displaying transformed images
display_images(trans_scaled_images)
# transforming images by (500,500 translation) and (2,2) scaling
trans_scaled_images = translation_scaling_without_opencv(images_list, (500,500), (2,2))
# displaying transformed images
display_images(trans_scaled_images)
# transforming images by (500,500 translation) and (0.5,0.5) scaling
trans_scaled_images = translation_scaling_without_opencv(images_list, (500,500), (0.5,0.5))
# displaying transformed images
display_images(trans_scaled_images)
display_images(images_list)
In this task, I will use the functions I created above thereby taking the benefit of a modular code :)
# function to combine Rotation (R), Translation (T) and Scaling (S) operations in the order: R --> T and S
def rotation_translation_scaling(images_list, angle_deg, translation=(0, 0), scaling=(1, 1)):
'''
Inputs:
1. images_list ==> list of images
2. angle_deg ==> angle by which we wish to rotate our image (in degrees)
3. translation ==> translation factors (dx, dy) [(0,0) by default for No Translation]
4. scaling ==> scaling factors (sx, sy) [(1,1) by default for No Scaling]
'''
# perform rotation first
rotated_images=rotation_without_using_opencv(images_list, angle_deg)
# perform translation and scaling on rotated images
final_images=translation_scaling_without_opencv(rotated_images, translation, scaling)
return final_images
# rotating images by 30 degrees followed by (100,100) translation and (2,2) scaling
mul_tran_images=rotation_translation_scaling(images_list, 30, (100,100), (2,2))
# displaying the transformed images
display_images(mul_tran_images)
# dimensions of the first transformed image
mul_tran_images[0].shape
(4612, 5112, 3)
# function to combine Translation (T) and Scaling (S) and Rotation (R) operations in the order: T and S --> R
def translation_scaling_rotation(images_list, angle_deg, translation=(0, 0), scaling=(1, 1)):
'''
Inputs:
1. images_list ==> list of images
2. angle_deg ==> angle by which we wish to rotate our image (in degrees)
3. translation ==> translation factors (dx, dy) [(0,0) by default for No Translation]
4. scaling ==> scaling factors (sx, sy) [(1,1) by default for No Scaling]
'''
# perform translation and scaling first
tran_scaled_images=translation_scaling_without_opencv(images_list, translation, scaling)
# perform rotation on the tranlated and scaled images
final_images=rotation_without_using_opencv(tran_scaled_images, angle_deg)
return final_images
# transforming images by (100,100) translation and (2,2) scaling followed by rotation of 30 degrees
mul_tran_images=translation_scaling_rotation(images_list, 30, (100,100), (2,2))
# displaying the transformed images
display_images(mul_tran_images)
# dimensions of the first transformed image
mul_tran_images[0].shape
(4685, 5185, 3)